package p39_boot执行流程;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * springboot启动之构造
 * SpringApplication.run(BootConstructor.class, args);
 * 上面代码启动一个boot项目，内部做了两件事(SpringApplication类的内部)
 *   1.new SpringApplication(primarySources)：创建SpringApplication对象
 *   2.调用对象的run方法
 * 这节介绍第一步构造中做了什么事
 */
@Configuration
public class BootConstructor {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        //SpringApplication.run(BootConstructor.class, args);
        /**
         * 1.获取BeanDefinition源
         * 会将加了@SpringBootApplication注解的类作为主Bean Definition源
         * SpringBootApplication本质也是一个配置类
         * 也可以添加额外的BeanDefinition源
         * 这里仅仅是获取到源，还没有开始初始化源里的BeanDefinition
         */
        System.out.println("1.获取主Bean Definition源");
        SpringApplication spring = new SpringApplication(BootConstructor.class);
        //额外添加源
        spring.setSources(Set.of("classpath:p02/TestPathXml.xml"));

        /**
         * 2.推断该应用类型
         * 启动时根据类路径下某些关键class类是否存在，判断该应用是什么类型
         * 有三种类型(通过jar的关键类来判断)：1.非web程序；2.servlet的web程序；3.基于reactive的web程序
         * 使用了如下方式推断
         */
        System.out.println("2.推断该应用类型");
        Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
        deduceFromClasspath.setAccessible(true);
        Object type = deduceFromClasspath.invoke(null);
        //当前应用加了web-starter,会被认为是SERVLET类型
        System.out.println("应用类型===>" + type);

        /**
         * 3.添加一些初始化器
         *   ApplicationContext的refresh方法时，会回调初始化器的方法
         *   初始化器就是在这里添加的
         * 默认从配置文件中读取，这里手动添加
         */
        System.out.println("3.添加Application初始化器");
        spring.addInitializers(applicationContext -> {
            //将来ApplicationContext的refresh方法，就会回调这个方法，并把容器对象作为参数
            GenericApplicationContext context = (GenericApplicationContext)applicationContext;
            context.registerBean("bean3", String.class);
        });

        /**
         * 4.添加监听器与事件，默认从配置文件中读取
         * 这里手动添加
         * 将来spring.run(args)中产生的事件(事件类型是ApplicationEvent)，就会回调这里添加的onApplicationEvent方法
         */
        System.out.println("4.添加监听器与事件");
        spring.addListeners(new ApplicationListener<ApplicationEvent>() {
            private int i;
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println("\t监听到事件:===>" + event.getClass());
            }
        }, event -> {
            System.out.println("fdsfdsfsdfds");
        });
        /**
         * 5.主类推断
         * 判断main方法所在类
         */
        System.out.println("5.主类推断");
        Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass");
        deduceMainApplicationClass.setAccessible(true);
        Object mainClass = deduceMainApplicationClass.invoke(spring);
        System.out.println("主类是===>" + mainClass);

        Field listeners = spring.getClass().getDeclaredField("listeners");
        listeners.setAccessible(true);
        Object o = listeners.get(spring);
        System.out.println(o);

        /**
         * boot启动的构造环节只是做了准备工作，并没把spring容器真正创建出来，只是得到一些东西、添加一些东西、做下推断而已
         * 只有调用run方法时才会创建容器
         */
        ConfigurableApplicationContext context = spring.run(args);
        Field listeners1 = spring.getClass().getDeclaredField("listeners");
        listeners1.setAccessible(true);
        Object o1 = listeners1.get(spring);
        System.out.println(o1);

        for (String name : context.getBeanDefinitionNames()) {
            //获取这个bean来自哪里
            String resourceDescription = context.getBeanFactory().getBeanDefinition(name).getResourceDescription();
            System.out.println(name + "--->" + resourceDescription);
        }

        context.close();

    }

    @Component("bean1")
    static class Bean1 {}

    @Bean
    public Bean1 bean2() {
        return new Bean1();
    }

    //基于servlet的web环境启动时，必须要有ServletWebServerFactory
    @Bean
    public TomcatServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}
